package profile

import (
	"errors"
	"fmt"
	"gitee.com/zackeus/go-boot/freeswitch/mod/xmlcurl"
	"gitee.com/zackeus/goutil/arrutil"
	"gitee.com/zackeus/goutil/strutil"
	"github.com/beevik/etree"
	"strconv"
)

type (
	Option func(xml *Xml) error

	Xml struct {
		doc      *etree.Document
		settings *etree.Element
	}
)

var (
	allowedMultipleRegistrations = []string{"contact", "true", "false"}
)

func New(name string, port string, ctxName string, opts ...Option) (xmlcurl.XmlConf, error) {
	xml := &Xml{
		doc: etree.NewDocument(),
	}
	profile := xml.doc.CreateElement("profile")
	profile.CreateAttr("name", name)

	/* 网关列表 */
	gateways := profile.CreateElement("gateways")
	gatewayProcess := gateways.CreateElement("X-PRE-PROCESS")
	gatewayProcess.CreateAttr("cmd", "include")
	gatewayProcess.CreateAttr("data", fmt.Sprintf("%s/*.xml", name))

	domains := profile.CreateElement("domains")
	domain := domains.CreateElement("domain")
	domain.CreateAttr("name", "all")
	domain.CreateAttr("alias", "false")
	domain.CreateAttr("parse", "true")

	settings := profile.CreateElement("settings")
	xml.settings = settings

	/* SIP 端口号 */
	xml.createParam("sip-port", port)

	/* 默认自适应本机IP */
	xml.createParam("rtp-ip", "$${local_ip_v4}")
	xml.createParam("sip-ip", "$${local_ip_v4}")
	xml.createParam("ext-rtp-ip", "autonat:$${local_ip_v4}")
	xml.createParam("ext-sip-ip", "autonat:$${local_ip_v4}")

	/* 路由组 */
	xml.createParam("context", ctxName)

	/* 超时时间 = 注册时刻系统时间now + x + y */
	/* 如果配置了 sip-force-expires 则 x 为配置的值 否则从 sip客户端注册时提交的信息中获取expires */
	/* sip-expires-max-deviation 会向200 ok中的expires值添加一个随机偏差, 响应的过期时间将介于300-30 = 270 和 300 + 30 = 330秒之间, 防止注册风暴 */
	//xml.createParam("sip-force-expires", "300")
	//xml.createParam("sip-expires-max-deviation", "30")

	/* 支持的语音编码，用于语音编码协商 */
	// xml.createParam("inbound-codec-prefs", "$${global_codec_prefs}")
	// xml.createParam("outbound-codec-prefs", "$${global_codec_prefs}")
	xml.createParam("codec-prefs", "$${codec_prefs}")
	xml.createParam("inbound-codec-negotiation", "generous")
	/* 是否晚协商 */
	xml.createParam("inbound-late-negotiation", strconv.FormatBool(false))
	xml.createParam("inbound-zrtp-passthru", strconv.FormatBool(false))

	xml.createParam("user-agent-string", "Yulon-VoiceStar-v1.0.0/UA 2.0")
	xml.createParam("debug", "0")
	xml.createParam("sip-trace", "no")
	xml.createParam("sip-capture", "no")
	xml.createParam("rtp-timer-name", "none")
	xml.createParam("inbound-reg-force-matching-username", strconv.FormatBool(true))
	xml.createParam("nonce-ttl", "60")
	xml.createParam("rfc2833-pt", "101")
	xml.createParam("inbound-reg-in-new-thread", strconv.FormatBool(true))
	xml.createParam("enable-100rel", strconv.FormatBool(true))
	xml.createParam("send-display-update", strconv.FormatBool(false))
	xml.createParam("dialplan", "XML")
	xml.createParam("dtmf-duration", "2000")
	xml.createParam("hold-music", "$${hold_music}")
	xml.createParam("odbc-dsn", "$${odbc-dsn}")

	xml.createParam("manage-presence", strconv.FormatBool(false))
	xml.createParam("track-calls", strconv.FormatBool(false))

	for _, opt := range opts {
		if err := opt(xml); err != nil {
			return nil, err
		}
	}
	return xml, nil
}

func (x *Xml) createParam(name string, value string) {
	param := x.settings.FindElement(fmt.Sprintf("./param[@name='%s']", name))
	if param == nil {
		param = x.settings.CreateElement("param")
	}

	param.CreateAttr("name", name)
	param.CreateAttr("value", value)
}

func (x *Xml) removeParam(name string) {
	param := x.settings.FindElement(fmt.Sprintf("./param[@name='%s']", name))
	if param != nil {
		x.settings.RemoveChild(param)
	}
}

func WithIp(ip string) Option {
	return func(x *Xml) error {
		if strutil.IsNotBlank(ip) {
			x.createParam("rtp-ip", ip)
			x.createParam("sip-ip", ip)
		}
		return nil
	}
}

func WithExtIp(ip string) Option {
	return func(x *Xml) error {
		if strutil.IsNotBlank(ip) {
			f := fmt.Sprintf("autonat:%s", ip)
			x.createParam("ext-rtp-ip", f)
			x.createParam("ext-sip-ip", f)
		}
		return nil
	}
}

// WithLogAuthFailures 记录失败授权
func WithLogAuthFailures(b bool) Option {
	return func(x *Xml) error {
		x.createParam("log-auth-failures", strconv.FormatBool(b))
		return nil
	}
}

// WithEnableCompactHeaders 紧凑头
func WithEnableCompactHeaders(b bool) Option {
	return func(x *Xml) error {
		x.createParam("enable-compact-headers", strconv.FormatBool(b))
		return nil
	}
}

// WithAuthCalls 通话鉴权
func WithAuthCalls(b bool) Option {
	return func(x *Xml) error {
		x.createParam("auth-calls", strconv.FormatBool(b))
		return nil
	}
}

// WithOptionsPing 保持SIP Ping
func WithOptionsPing(b bool) Option {
	return func(x *Xml) error {
		x.createParam("all-reg-options-ping", strconv.FormatBool(b))
		x.createParam("nat-options-ping", strconv.FormatBool(b))
		/* 如果 nat-options-ping 设置为 True, OPTIONS 数据包上没有应答，则端点将被注销 */
		x.createParam("unregister-on-options-fail", strconv.FormatBool(b))

		if b {
			/* 向注册用户发送 OPTIONS 数据包的平均间隔(s) */
			x.createParam("ping-mean-interval", "60")
			/* 控制同时发送 ping 的频率 */
			/* 示例：间隔设置为 30，频率设置为 1，对于 1000 个注册用户，FS 将每秒 ping 33 个用户，并且每 30 秒重新启动一次 (1000 除以 30 = 33) */
			x.createParam("ping-thread-frequency", "1")
		} else {
			x.removeParam("ping-mean-interval")
			x.removeParam("ping-thread-frequency")
		}
		return nil
	}
}

// WithAggressiveNatDetection 注册时检测NAT
func WithAggressiveNatDetection(b bool) Option {
	return func(x *Xml) error {
		x.createParam("aggressive-nat-detection", strconv.FormatBool(b))
		return nil
	}
}

// WithForceRport 强制使用rport
func WithForceRport(b bool) Option {
	return func(x *Xml) error {
		x.createParam("NDLB-force-rport", strconv.FormatBool(b))
		return nil
	}
}

// WithEnableTimer sip 时钟
func WithEnableTimer(b bool) Option {
	return func(x *Xml) error {
		x.createParam("enable-timer", strconv.FormatBool(b))
		return nil
	}
}

// WithForceDomain 强制单域
func WithForceDomain(b bool) Option {
	return func(x *Xml) error {
		if b {
			x.createParam("force-register-domain", "$${domain}")
			x.createParam("force-subscription-domain", "$${domain}")
			x.createParam("force-register-db-domain", "$${domain}")
		} else {
			x.removeParam("force-register-domain")
			x.removeParam("force-subscription-domain")
			x.removeParam("force-register-db-domain")
		}

		return nil
	}
}

// WithTrackCalls 呼叫跟踪 会将通话数据写入数据库
func WithTrackCalls(b bool) Option {
	return func(x *Xml) error {
		x.createParam("track-calls", strconv.FormatBool(b))
		return nil
	}
}

// WithManagePresence 支持列席
func WithManagePresence(b bool) Option {
	return func(x *Xml) error {
		if b {
			x.createParam("presence-hosts", "$${domain},$${local_ip_v4}")
			x.createParam("presence-privacy", "$${presence_privacy}")
		} else {
			x.removeParam("presence-hosts")
			x.removeParam("presence-privacy")
		}

		x.createParam("manage-presence", strconv.FormatBool(b))
		// x.createParam("dbname", "share_presence")
		return nil
	}
}

// WithRegistrationThreadFrequency 检查注册是否有效的频率
func WithRegistrationThreadFrequency(v int64) Option {
	return func(x *Xml) error {
		x.createParam("registration-thread-frequency", strconv.FormatInt(v, 10))
		return nil
	}
}

// WithDtmfType DTMF 类型
func WithDtmfType(dtmf string) Option {
	return func(x *Xml) error {
		x.createParam("dtmf-type", dtmf)
		return nil
	}
}

// WithCallerIdType 主叫格式
func WithCallerIdType(t string) Option {
	return func(x *Xml) error {
		x.createParam("caller-id-type", t)
		return nil
	}
}

// WithMultipleRegistrations 设置为“ contact”将删除基于sip_user，sip_host和contact字段（而不是call_id）的旧注册
// 有效值为“ contact”，“ true”，“ false”
func WithMultipleRegistrations(v string) Option {
	return func(x *Xml) error {
		if strutil.IsBlank(v) || arrutil.NotIn(v, allowedMultipleRegistrations) {
			return errors.New("the multiple-registrations is invalid")
		}
		x.createParam("multiple-registrations", v)
		return nil
	}
}

// WithMaxRegistrationsPerExtension 定义相同user最大注册数。此参数的有效值是大于0的整数。请注意，将此值设置为1会抵消对multi -registrations的使用
func WithMaxRegistrationsPerExtension(v uint64) Option {
	return func(x *Xml) error {
		if v <= 0 {
			v = 1
		}

		if v == 1 {
			x.createParam("multiple-registrations", strconv.FormatBool(false))
		} else {
			x.createParam("multiple-registrations", strconv.FormatBool(true))
		}
		x.createParam("max-registrations-per-extension", strconv.FormatUint(v, 10))
		return nil
	}
}

// WithWS ws 端口
func WithWS(port string) Option {
	return func(x *Xml) error {
		if strutil.IsBlank(port) {
			x.removeParam("ws-binding")
			return nil
		}

		x.createParam("ws-binding", fmt.Sprintf(":%s", port))
		return nil
	}
}

// WithWSS wss 端口
func WithWSS(port string) Option {
	return func(x *Xml) error {
		if strutil.IsBlank(port) {
			x.removeParam("wss-binding")
			return nil
		}

		x.createParam("wss-binding", fmt.Sprintf(":%s", port))
		return nil
	}
}

// WithApplyNatAcl 内网网段 此网段范围会自动启用NAT模式
func WithApplyNatAcl(acl string) Option {
	return func(x *Xml) error {
		if strutil.IsBlank(acl) {
			x.removeParam("apply-nat-acl")
			return nil
		}
		x.createParam("apply-nat-acl", acl)
		return nil
	}
}

// WithApplyRegisterAcl 注册权限网段 可以不提供密码
func WithApplyRegisterAcl(acl string) Option {
	return func(x *Xml) error {
		if strutil.IsBlank(acl) {
			x.removeParam("apply-register-acl")
			return nil
		}
		x.createParam("apply-register-acl", acl)
		return nil
	}
}

// WithApplyInboundAcl 呼入权限网段 不会进行鉴权
func WithApplyInboundAcl(acl string) Option {
	return func(x *Xml) error {
		if strutil.IsBlank(acl) {
			x.removeParam("apply-inbound-acl")
			return nil
		}
		x.createParam("apply-inbound-acl", acl)
		return nil
	}
}

// WithApplyCandidateAcl ICE可选网段
func WithApplyCandidateAcl(acl string) Option {
	return func(x *Xml) error {
		if strutil.IsBlank(acl) {
			x.removeParam("apply-candidate-acl")
			return nil
		}
		x.createParam("apply-candidate-acl", acl)
		return nil
	}
}

func (x *Xml) BodyElement() *etree.Element {
	return x.settings
}

func (x *Xml) MarshalToXml() ([]byte, error) {
	x.doc.Indent(1)
	return x.doc.WriteToBytes()
}

func (x *Xml) UnMarshalFromString(v string) error {
	if x.doc == nil {
		x.doc = etree.NewDocument()
	}
	if err := x.doc.ReadFromString(v); err != nil {
		return err
	}

	settings := x.doc.FindElement("./profile/settings")
	if settings == nil {
		return errors.New("the element profile/settings is nil")
	}
	x.settings = settings

	return nil
}

func (x *Xml) WriteToFile(path string) error {
	x.doc.Indent(1)
	return x.doc.WriteToFile(path)
}
